﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Collections;

namespace Dukou.Javascript
{
    /// <summary>
    /// 值对象
    /// </summary>
    [Serializable]
    public abstract class JsValue : DynamicObject
    {
        private IDictionary<string, JsValue> Data = new Dictionary<string, JsValue>();

        public JsValue this[string key]
        {
            get
            {
                return this.Data[key];
            }
            set
            {
                this.Data[key] = value;
            }
        }

        public JsValue()
        {
 
        }

        public JsValue(object value)
            : this("", value)
        {
        }

        public JsValue(string name, object value)
        {
            this.__VALUE_NAME = name ?? "";
            if (value != null && typeof(JsValue).IsAssignableFrom(value.GetType()))
            {
                JsValue jsValue = (JsValue)value;
                this.__ORIGINAL_VALUE = jsValue.__ORIGINAL_VALUE;
                this.__VALUE_TYPE = jsValue.__VALUE_TYPE;
            }
            else
            {
                this.__ORIGINAL_VALUE = value;
                this.__VALUE_TYPE = GetJsType(value);
            }

            if (this.__VALUE_TYPE == JsType.Object)
            {
                IList<PropertyInfo> properties = new List<PropertyInfo>();
                Type valueType = this.__ORIGINAL_VALUE.GetType();
                if (valueType.Name.StartsWith("<>f__AnonymousType"))
                {
                    properties = valueType.GetProperties()
                        .Where(x => x.Name != "__ORIGINAL_VALUE" || x.Name != "__VALUE_TYPE").ToList();
                }
                else
                {
                    properties = valueType.GetProperties()
                       .Where(x => x.Name != "__ORIGINAL_VALUE" || x.Name != "__VALUE_TYPE").ToList();
                }
                if (properties != null)
                {
                    foreach (PropertyInfo item in properties)
                    {
                        this[item.Name] = JsValue.Create(item.Name, item.GetValue(this.__ORIGINAL_VALUE, null));
                    }
                }
            }
            if (this.__VALUE_TYPE == JsType.Array)
            {
                IEnumerable enumerable = this.__ORIGINAL_VALUE as IEnumerable;
                if (enumerable != null)
                {
                    IEnumerator enumerator = enumerable.GetEnumerator();
                    if (enumerator != null)
                    {
                        int i = 0;
                        while (enumerator.MoveNext())
                        {
                            this[i.ToString()] = JsValue.Create(enumerator.Current);
                            i++;
                        }
                    }
                }

            }
        }

        public object __ORIGINAL_VALUE { get; protected set; }

        public string __VALUE_NAME { get; protected set; }

        public JsType __VALUE_TYPE { get; protected set; }

        public override string ToString()
        {
            switch (this.__VALUE_TYPE)
            {
                case JsType.None:
                    break;
                case JsType.Null:
                    return "null";
                case JsType.Undefined:
                    return "undefined";
                case JsType.Number:
                    return this.__ORIGINAL_VALUE.ToString();
                case JsType.String:
                    return string.Format("'{0}'", this.__ORIGINAL_VALUE);
                case JsType.Object:
                    {
                        int count = 1;
                        StringBuilder builder = new StringBuilder("{");
                        foreach (var item in this.Data)
	                    {
                            builder.Append(string.Format("{0}: {1}{2}", item.Key, item.Value, (count == this.Data.Count ? "" : ",")));
                            count++;
	                    }
                        builder.Append("}");
                        return builder.ToString();
                    }
                case JsType.Array:
                    {
                        int count = 1;
                        StringBuilder builder = new StringBuilder("[");
                        foreach (var item in this.Data)
                        {
                            builder.Append(string.Format("{0}{1}", item.Value, (count == this.Data.Count ? "" : ",")));
                            count++;
                        }
                        builder.Append("]");
                        return builder.ToString();
                    }
                default:
                    break;
            }

            return base.ToString();
        }

        public static bool IsNumber(object value)
        {
            if (value == null)
            {
                return false;
            }
            Type type = value.GetType();
            if (type == typeof(int) || type == typeof(long)
                || type == typeof(decimal) || type == typeof(float)
                || type == typeof(double))
            {
                return true;
            }

            return false;
        }

        public static bool IsString(object value)
        {
            if (value == null)
            {
                return false;
            }
            Type type = value.GetType();
            if (type == typeof(string))
            {
                return true;
            }

            return false;
        }

        public static bool IsObject(object value)
        {
            if (value == null)
            {
                return false;
            }
            Type type = value.GetType();
            return type.IsClass;
        }

        public static bool IsArray(object value)
        {
            if (value == null)
            {
                return false;
            }

            IEnumerable e = value as IEnumerable;
            return e != null;
        }

        public static bool IsOriginalType(object value)
        {
            if (value == null)
            {
                return false;
            }
            Type type = value.GetType();
            if (type == typeof(int) || type == typeof(long)
                || type == typeof(float) || type == typeof(double)
                || type == typeof(decimal) || type == typeof(string))
            {
                return true;
            }

            return false;
        }

        public static JsType GetJsType(object value)
        {
            if (value == null)
            {
                return JsType.Null;
            }

            Type valueType = value.GetType();
            if (valueType == typeof(JsNull))
            {
                return JsType.Null;
            }
            else if (valueType == typeof(JsUndefined))
            {
                return JsType.Undefined;
            }
            else if (valueType == typeof(JsNumber))
            {
                return JsType.Number;
            }
            else if (valueType == typeof(JsString))
            {
                return JsType.String;
            }
            else if (valueType == typeof(JsObject))
            {
                return JsType.Object;
            }
            else if (valueType == typeof(JsArray))
            {
                return JsType.Array;
            }
            else if (valueType == typeof(JsFunction))
            {
                return JsType.Function;
            }
            else if (valueType == typeof(String))
            {
                return JsType.String;
            }
            else if (IsNumber(value))
            {
                return JsType.Number;
            }
            else if (IsArray(value))
            {
                return JsType.Array;
            }
            else if (IsObject(value))
            {
                return JsType.Object;
            }
            else
            {
                return JsType.None;
            }

        }

        public static JsValue Create(object value)
        {
            return Create("", value);
        }

        private static JsValue Create(string name, object value)
        {
            if (value == null)
            {
                return Null(name);
            }

            if (typeof(JsValue).IsAssignableFrom(value.GetType()))
            {
                return (JsValue)value;
            }
            else if (IsNumber(value))
            {
                return Number(name, Convert.ToDecimal(value));
            }
            else if (IsString(value))
            {
                return String(name, value.ToString());
            }
            else if (IsArray(value))
            {
                return Array(name, value);
            }
            else if (IsObject(value))
            {
                return Object(name, value);
            }

            return null;
        }

        private static JsValue Null(string name)
        {
            return new JsNull(name);
        }

        private static JsValue Undefined(string name)
        {
            return new JsUndefined(name);
        }

        private static JsValue Number(string name, int value)
        {
            return new JsNumber(name, value);
        }

        private static JsValue Number(string name, long value)
        {
            return new JsNumber(name, value);
        }

        private static JsValue Number(string name, float value)
        {
            return new JsNumber(name, value);
        }

        private static JsValue Number(string name, double value)
        {
            return new JsNumber(name, value);
        }

        private static JsValue Number(string name, decimal value)
        {
            return new JsNumber(name, value);
        }

        private static JsValue String(string name, string value)
        {
            return new JsString(name, value);
        }

        private static JsValue Object(string name, object value)
        {
            return new JsObject(name, value);
        }

        private static JsValue Array(string name, object value)
        {
            return new JsArray(name, value);
        }

        #region DynamicObject

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            this.Data[binder.Name] = (JsValue)value;
            return true;

        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = this.Data[binder.Name];
            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return this.Data.Keys;
        }

        #endregion
    }
}
